# database/module.mk
# Part of Lorica, a cash ordering system.
#
# Copyright © 2007–2008 Cybersource Pty. Ltd.
#
# This is free software: see the grant of license at end of this file.

# Makefile module for Lorica database.

MODULE_DIR = database

SQL_SUFFIX = .sql
APPLY_STAMP_SUFFIX = -apply
DATABASE_REVISIONS_DIR = ${DATABASE_DIR}/revisions
database_revision_files_regex = [[:digit:]]\+-[^/]\+${SQL_SUFFIX}
database_revision_files = $(shell \
	ls -1 ${DATABASE_REVISIONS_DIR}/*${SQL_SUFFIX} \
	| grep '${database_revision_files_regex}$$')
database_revision_targets = $(addsuffix ${APPLY_STAMP_SUFFIX},${database_revision_files})
DATABASE_REVISION_SETUP_DIR = ${DATABASE_REVISIONS_DIR}/setup
database_revision_setup_files = $(wildcard ${DATABASE_REVISION_SETUP_DIR}/*${SQL_SUFFIX})
database_revision_setup_targets = $(addsuffix ${APPLY_STAMP_SUFFIX},${database_revision_setup_files})

DATABASE_STAMP_FILES += ${DATABASE_REVISIONS_DIR}/*${APPLY_STAMP_SUFFIX}
DATABASE_STAMP_FILES += ${DATABASE_REVISION_SETUP_DIR}/*${APPLY_STAMP_SUFFIX}

PG_TMP_ROOT_DIR ?= ${DATABASE_DIR}/postgres-tmp

pg_sock_link = ${PG_TMP_ROOT_DIR}/run
PG_SOCK_DIR ?= $(shell \
	readlink ${pg_sock_link} || echo "/nonexistent" \
	)

PG_LOG_DIR ?= ${PG_TMP_ROOT_DIR}/log
PG_LOG_NAME ?= postgresql.log
pg_log_file = ${PG_LOG_DIR}/${PG_LOG_NAME}
PG_START_TIMEOUT_SECS = 10

PG_DATABASE_COMMENT = "Lorica cash ordering system database"

export PGDATABASE ?= lorica
export PGDATA ?= ${PG_TMP_ROOT_DIR}/data
export PGHOST ?= ${PG_SOCK_DIR}

DATABASE_ENV_FILE = ${DATABASE_DIR}/pg_env.sh

pg_pid_file = ${PGDATA}/postmaster.pid

DATABASE_TEST_DIR = $(DATABASE_DIR)/test

DATABASE_TEST_INWAIT_FILES = ${DATABASE_REVISIONS_DIR} ${DATABASE_TEST_DIR}

INSTALL = install
RM = rm

# Update this as necessary.
PG_VERSION = 8.3

# This format required on Debian-based systems.
#PG_BIN_DIR = /usr/lib/postgresql/$(PG_VERSION)/bin
# Most other systems.
PG_BIN_DIR = /usr/bin

INITDB = ${PG_BIN_DIR}/initdb
INITDB_OPTS = -A "ident sameuser"
PG_CTL = ${PG_BIN_DIR}/pg_ctl
PG_CTL_START_OPTS = -w -o "-h \"\" -k ${PG_SOCK_DIR}" -l ${pg_log_file}
PG_CTL_STOP_OPTS = -w -m fast
CREATEDB = ${PG_BIN_DIR}/createdb
CREATELANG = ${PG_BIN_DIR}/createlang
DROPDB = ${PG_BIN_DIR}/dropdb
PSQL = ${PG_BIN_DIR}/psql
APPLY_REVISION = ${CODE_PROGRAM_DIR}/apply-schema-revision

DATABASE_NOSETESTS_OPTS =


.PHONY: database-build-test
database-build-test: database-setup-stamp database-build-stamp database-test database-clean


${PGDATA}:
	$(INITDB) ${INITDB_OPTS}

GENERATED_FILES += database-setup-stamp
.PHONY: database-setup
database-setup: database-setup-stamp
database-setup-stamp: ${PGDATA} database-start
	touch $@


# usage: $(call database-test-server-started)
define database-test-server-started
       ( $(PG_CTL) status > /dev/null )
endef

# usage: $(call database-start-server)
define database-start-server
       $(PG_CTL) ${PG_CTL_START_OPTS} start
endef

# usage: $(call database-wait-sever-started,message)
define database-wait-server-started
	echo -n ${1} ; \
	for (( sec=0 ; $$sec < ${PG_START_TIMEOUT_SECS} ; sec++ )) ; do \
		$(call database-test-server-started) && break ; \
		echo -n "." ; \
		sleep 1 ; \
	done ; \
	echo
endef

ifeq (${PGHOST},${PG_SOCK_DIR})
database-start: ${pg_pid_file}
${pg_pid_file}: PGHOST =
${pg_pid_file}: ${PG_SOCK_DIR} ${PG_LOG_DIR}
endif

.PHONY: database-start
database-start:
	if ( $(call database-test-server-started) ; test $$? -ne 0 ) ; then \
		$(PG_CTL) ${PG_CTL_START_OPTS} start ; \
		sleep 2 ; \
	fi

	$(call database-wait-server-started, \
		"waiting for server to be active: ")
	$(PG_CTL) status

.PHONY: database-stop
database-stop: | database-drop
	if $(call database-test-server-started) ; then \
		$(PG_CTL) ${PG_CTL_STOP_OPTS} stop ; \
	fi
	$(RM) -rf ${PG_SOCK_DIR} ${pg_sock_link}

${PG_SOCK_DIR}: ${PGDATA} ${pg_sock_link}

${pg_sock_link}:
	ln -s $$(mktemp -td "postgres-${PGDATABASE}.XXXXXXXX") $@

${PG_LOG_DIR}:
	$(INSTALL) -d $@


# usage: $(call database-test-database-created)
define database-test-database-created
	( $(PSQL) -f /dev/null > /dev/null 2>&1)
endef

# usage: $(call database-write-env-file)
define database-write-env-file
	echo "# ${DATABASE_ENV_FILE}" > ${DATABASE_ENV_FILE}
	echo "# Database connection environment settings" >> ${DATABASE_ENV_FILE}
	echo "# Usage:" >> ${DATABASE_ENV_FILE}
	echo "#   $$ source ${DATABASE_ENV_FILE}" >> ${DATABASE_ENV_FILE}
	echo "#   $$ psql" >> ${DATABASE_ENV_FILE}
	echo >> ${DATABASE_ENV_FILE}
	echo "export PGDATABASE=${PGDATABASE}" >> ${DATABASE_ENV_FILE}
	echo "export PGDATA=${PGDATA}" >> ${DATABASE_ENV_FILE}
	echo "export PGHOST=${PGHOST}" >> ${DATABASE_ENV_FILE}
	echo "export PGPORT=${PGPORT}" >> ${DATABASE_ENV_FILE}
endef

${DATABASE_ENV_FILE}:
	$(call database-write-env-file)

GENERATED_FILES += database-create-stamp
.PHONY: database-create
database-create: database-create-stamp
database-create-stamp: ${DATABASE_ENV_FILE} | database-drop
	if ( $(call database-test-database-created) ; test $$? -ne 0 ) ; then \
		$(CREATEDB) ${PGDATABASE} ${PG_DATABASE_COMMENT} ; \
		$(CREATELANG) plpgsql ${PGDATABASE} ; \
	fi
	touch $@

${database_revision_setup_targets}: database-drop database-create

${DATABASE_REVISION_SETUP_DIR}/%${APPLY_STAMP_SUFFIX}: ${DATABASE_REVISION_SETUP_DIR}/%
	$(PSQL) -f $<
	touch $@

${database_revision_targets}: ${database_revision_setup_targets}

${DATABASE_REVISIONS_DIR}/%${APPLY_STAMP_SUFFIX}: ${DATABASE_REVISIONS_DIR}/%
	$(APPLY_REVISION) ${PGDATABASE} $<
	touch $@

GENERATED_FILES += database-build-stamp
.PHONY: database-build
database-build: database-build-stamp
database-build-stamp: ${database_revision_targets}
	touch $@

build: database-build


.PHONY: database-nosetests
database-nosetests: NOSETESTS_OPTS += ${DATABASE_NOSETESTS_OPTS}
database-nosetests: NOSETESTS_FILES = ${DATABASE_TEST_DIR}
database-nosetests: database-build-stamp
	$(call test-output-banner, "Database test run: ")
	$(nosetests_cmd)

.PHONY: database-test
database-test: database-nosetests

.PHONY: database-build-test-continuous
database-build-test-continuous: TEST_INWAIT_FILES = ${DATABASE_TEST_INWAIT_FILES}
database-build-test-continuous:
	while true ; do \
		clear ; \
		$(MAKE) database-build-test ; \
		$(call test-wait) ; \
	done

# Continuous rebuild of the test database.
#
# Note: The touch on database/test/stamp-IGNORE.py is a kludge -
# intended to signal a continuous 'make database-test' loop (hopefully
# running in another window) to rerun the database tests.
.PHONY: database-build-continuous
database-build-continuous:
	while true ; do \
		clear ; \
		unset PGDATABASE ; \
		unset PGDATA ; \
		unset PGHOST ; \
		echo -n "Database build run: " ; \
		$(DATE) +${DATE_FORMAT} ; \
		$(MAKE) clean && \
			$(MAKE) database-build && \
			echo '===> Database successfully rebuilt.' && \
			TEST_FILE_STAMP='database/test/stamp-IGNORE.py' && \
			echo > "$$TEST_FILE_STAMP" && \
			rm -f "$$TEST_FILE_STAMP" ; \
		$(INWAIT) ${INWAIT_OPTS} ${DATABASE_REVISIONS_DIR} ; \
	done

build: database-test


%.png: %.dot
	$(DOT) -Tpng -o $@ $<

# Requires the environment variables set up to access the database.
$(PGDATABASE).dot: database-build
	$(POSTGRESQL_AUTODOC) -t dot

database-diagram: $(PGDATABASE).png
	@echo "Generated $<"
	@if [ ! -z "$(DISPLAY)" ] ; then display $< ; fi


.PHONY: database-drop
database-drop: database-clean
	if ( $(call database-test-database-created) ) ; then \
		$(DROPDB) ${PGDATABASE} ; \
	fi
	$(RM) -f database-create-stamp

.PHONY: database-teardown
database-teardown: database-drop database-stop
	$(RM) -rf ${PG_TMP_ROOT_DIR}

database-clean: database-drop
	$(RM) -f ${DATABASE_STAMP_FILES}

clean: database-clean


# This is free software: you may copy, modify, and/or distribute this work
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; version 3 of that license or any later version.
#
# No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details,
# or view it online at <URL:https://www.gnu.org/licenses/gpl-3.0.html>.


# Local variables:
# coding: utf-8
# mode: makefile
# End:
# vim: fileencoding=utf-8 filetype=make :
